package com.gopay.bis.cmbc.expay.operation;

import java.io.ByteArrayOutputStream;
import java.security.KeyFactory;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sun.misc.BASE64Decoder;

import com.gopay.common.constants.proccode.ProcCodeConstants;
import com.gopay.common.exception.GopayException;

/**
 * RSAHelper - 对RSA 签名&验签/分段加密&分段解密 的包装 签名算法: "SHA1withRSA", 私钥进行签名; 公钥进行验签.
 * 加密算法: "RSA/ECB/PKCS1Padding", 公钥进行加密; 私钥进行解密.
 * 
 * [localPrivKey]是自己的私钥, 自己的公钥给通信对方. [peerPubKey]是对方的公钥, 对方的私钥在对方那边. 为了方便,
 * 这里假定双方的密钥长度一致, 签名和加密的规则也一致.
 * 
 * 以`Base64Str`结尾的参数表示内容是Base64编码的字符串, 其他情况都是raw字符串.
 */

public class RSAHelper {
    private final static Logger logger = LoggerFactory.getLogger(RSAHelper.class);
    public static final String KEY_ALGORITHM = "RSA";
    public static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
    public static final String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding"; // 加密block需要预留11字节
    public static final int KEYBIT = 2048;
    public static final int RESERVEBYTES = 11;

    /**
     * RAS加密
     * 
     * @param publicKeyStr
     *            公钥
     * @param plainBytes
     *            待加密信息
     * @return byte[]
     * @throws GopayException
     */
    public byte[] encryptRSA(String publicKeyStr, byte[] plainBytes, boolean useBase64Code, String charset)
            throws GopayException {
        try{
            logger.error("RSAHelper encryptRSA plain: " + new String(plainBytes, "UTF-8"));
            BASE64Decoder base64Decoder = new BASE64Decoder();
            byte[] buffer = base64Decoder.decodeBuffer(publicKeyStr);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
            RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);

            String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding"; // 加密block需要预留11字节
            int KEYBIT = 2048;
            int RESERVEBYTES = 11;
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            int decryptBlock = KEYBIT / 8; // 256 bytes
            int encryptBlock = decryptBlock - RESERVEBYTES; // 245 bytes
            // 计算分段加密的block数 (向上取整)
            int nBlock = (plainBytes.length / encryptBlock);
            if ((plainBytes.length % encryptBlock) != 0) { // 余数非0，block数再加1
                nBlock += 1;
            }
            // 输出buffer, 大小为nBlock个decryptBlock
            ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * decryptBlock);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            // cryptedBase64Str =
            // Base64.encodeBase64String(cipher.doFinal(plaintext.getBytes()));
            // 分段加密
            for (int offset = 0; offset < plainBytes.length; offset += encryptBlock) {
                // block大小: encryptBlock 或 剩余字节数
                int inputLen = (plainBytes.length - offset);
                if (inputLen > encryptBlock) {
                    inputLen = encryptBlock;
                }
                // 得到分段加密结果
                byte[] encryptedBlock = cipher.doFinal(plainBytes, offset, inputLen);
                // 追加结果到输出buffer中
                outbuf.write(encryptedBlock);
            }
            // 如果是Base64编码，则返回Base64编码后的数组
            if (useBase64Code) {
                return Base64.encodeBase64String(outbuf.toByteArray()).getBytes(charset);
            } else {
                return outbuf.toByteArray(); // ciphertext
            }
        } catch (Exception e) {
            logger.error("RSAHelper encryptRSA error: ", e);
            throw new GopayException(ProcCodeConstants.PROC_CODE_100E6057);
        }
    }

    /**
     * RSA解密
     * 
     * @param privateKeyStr
     *            私钥
     * @param cryptedBytes
     *            待解密信息
     * @return byte[]
     * @throws Exception
     */
    public byte[] decryptRSA(String privateKeyStr, byte[] cryptedBytes, boolean useBase64Code, String charset)
            throws GopayException {
        try{
            logger.error("RSAHelper decryptRSA signLength: " + cryptedBytes.length);
            BASE64Decoder base64Decoder = new BASE64Decoder();
            byte[] buffer = base64Decoder.decodeBuffer(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);

            String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding"; // 加密block需要预留11字节
            byte[] data = null;

            // 如果是Base64编码的话，则要Base64解码
            if (useBase64Code) {
                data = Base64.decodeBase64(new String(cryptedBytes, charset));
            } else {
                data = cryptedBytes;
            }

            int KEYBIT = 2048;
            int RESERVEBYTES = 11;
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            int decryptBlock = KEYBIT / 8; // 256 bytes
            int encryptBlock = decryptBlock - RESERVEBYTES; // 245 bytes
            // 计算分段解密的block数 (理论上应该能整除)
            int nBlock = (data.length / decryptBlock);
            // 输出buffer, , 大小为nBlock个encryptBlock
            ByteArrayOutputStream outbuf = new ByteArrayOutputStream(nBlock * encryptBlock);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            // plaintext = new
            // String(cipher.doFinal(Base64.decodeBase64(cryptedBase64Str)));
            // 分段解密
            for (int offset = 0; offset < data.length; offset += decryptBlock) {
                // block大小: decryptBlock 或 剩余字节数
                int inputLen = (data.length - offset);
                if (inputLen > decryptBlock) {
                    inputLen = decryptBlock;
                }

                // 得到分段解密结果
                byte[] decryptedBlock = cipher.doFinal(data, offset, inputLen);
                // 追加结果到输出buffer中
                outbuf.write(decryptedBlock);
            }
            outbuf.flush();
            outbuf.close();
            return outbuf.toByteArray();
        }catch (Exception e) {
            logger.error("RSAHelper decryptRSA error: ", e);
            throw new GopayException(ProcCodeConstants.PROC_CODE_100E6057);
        }
    }

    /**
     * RSA签名
     * 
     * @param privateKeyStr
     *            私钥
     * @param plainBytes
     *            需要签名的信息
     * @return byte[]
     * @throws GopayException
     */
    public byte[] signRSA(String privateKeyStr, byte[] plainBytes, boolean useBase64Code, String charset) throws GopayException {
        try{
            logger.error("RSAHelper signRSA plain: " + new String(plainBytes, "UTF-8"));
            BASE64Decoder base64Decoder = new BASE64Decoder();
            byte[] buffer = base64Decoder.decodeBuffer(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            RSAPrivateKey privateKey = (RSAPrivateKey) keyFactory.generatePrivate(keySpec);

            String SIGNATURE_ALGORITHM = "SHA1withRSA";
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initSign(privateKey);
            signature.update(plainBytes);

            // 如果是Base64编码的话，需要对签名后的数组以Base64编码
            if (useBase64Code) {
                return Base64.encodeBase64String(signature.sign()).getBytes(charset);
            } else {
                return signature.sign();
            }
        } catch (Exception e) {
            logger.error("RSAHelper signRSA error: ", e);
            throw new GopayException(ProcCodeConstants.PROC_CODE_100E6057);
        }
    }

    /**
     * 验签操作
     * 
     * @param publicKeyStr
     *            公钥
     * @param plainBytes
     *            需要验签的信息
     * @param signBytes
     *            签名信息
     * @return boolean
     */
    public boolean verifyRSA(String publicKeyStr, byte[] plainBytes, byte[] signBytes, boolean useBase64Code,
            String charset) throws GopayException {
        try{
            logger.error("RSAHelper verifyRSA plain: " + new String(plainBytes, "UTF-8") + " signLength: "
                    + signBytes.length);
            BASE64Decoder base64Decoder = new BASE64Decoder();
            byte[] buffer = base64Decoder.decodeBuffer(publicKeyStr);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
            RSAPublicKey publicKey = (RSAPublicKey) keyFactory.generatePublic(keySpec);

            boolean isValid = false;
            String SIGNATURE_ALGORITHM = "SHA1withRSA";
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initVerify(publicKey);
            signature.update(plainBytes);

            // 如果是Base64编码的话，需要对验签的数组以Base64解码
            if (useBase64Code) {
                isValid = signature.verify(Base64.decodeBase64(new String(signBytes, charset)));
            } else {
                isValid = signature.verify(signBytes);
            }
            logger.error("RSAHelper verifyRSA result: " + isValid);
            return isValid;
        } catch (Exception e) {
            logger.error("RSAHelper verifyRSA error: ", e);
            throw new GopayException(ProcCodeConstants.PROC_CODE_100E6057);
        }
    }
    
}
